/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#include "ConfigWriter.h"

#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdexcept>
#include <sstream>
#include <algorithm>

#include "Utils.h"

// Przestrzeń nazw
namespace RafMat
{
namespace SimFile
{

using namespace std;

ConfigWriter::ConfigWriter(ostream& outStream)
    : m_outputStream(outStream),
      m_indentSize(4),
      m_indentLevel(0),
      m_commentAlignment(10),
      m_rows(0), m_columns(0),
      m_streamPos(0),
      m_enableComments(true)
{
    initializeBlockMap();

    m_alignmentSupport = (m_outputStream.tellp() >= 0);
}

ConfigWriter::~ConfigWriter()
{
    closeUnclosedBlocks();
    pushEmptyLine();
}

void ConfigWriter::initializeBlockMap()
{
    m_blockNames[BlockSimulation]   = "Simulation";
    m_blockNames[BlockModel]        = "Model";
    m_blockNames[BlockParams]       = "Params";
    m_blockNames[BlockInput]        = "Input";
    m_blockNames[BlockController]   = "Controller";
    m_blockNames[BlockNonlinearity] = "Nonlinearity";
}

void ConfigWriter::pushIndent()
{
    // Dodaje wcięcie.
    // Dodanie pustego łańcucha "" jest konieczne zeby uaktywnić
    // wypełnienie pola spacji.
    m_outputStream << setfill(' ') << setw(m_indentSize * m_indentLevel) << "";
    updateColumns();
}

void ConfigWriter::incrementIndent() throw()
{
    m_indentLevel++;
}

void ConfigWriter::decrementIndent() throw()
{
    if(m_indentLevel)
        --m_indentLevel;
}

void ConfigWriter::updateColumns()
{
    m_columns = static_cast<unsigned int>(m_outputStream.tellp()) - m_streamPos + 1;
}

void ConfigWriter::pushEmptyLine(unsigned int howMuch)
{
    while(howMuch--)
    {
        m_outputStream << endl;
        ++m_rows;
    }

    // Zapamiętaj pozycję w strumieniu.
    m_streamPos = m_outputStream.tellp();
    m_columns = 0;
}

void ConfigWriter::pushBlock(BlockType blockType, std::string blockName)  throw(conf_writer_error)
{
    bool result;
    BlockMap::const_iterator it = Utils::mapContains(m_blockNames, blockType, result);

    if(!result)
        throw conf_writer_error::badBlock(it->second);

    pushEmptyLine();
    pushIndent();

    m_outputStream << LBR_CHAR << ' ' << it->second << ' ' << blockName << ' ' << RBR_CHAR;
    incrementIndent();

    m_blockStack.push(blockType);
    updateColumns();
}

void ConfigWriter::closeBlock() throw (conf_writer_error)
{
    // Nic nie rób jeśli stos otwartych bloków jest pusty.
    if(m_blockStack.empty())
        return;

    BlockType blockType = m_blockStack.top();

    bool result;
    BlockMap::const_iterator it = Utils::mapContains(m_blockNames, blockType, result);

    if(!result)
        throw conf_writer_error::badBlock(it->second);

    pushEmptyLine();
    decrementIndent();
    pushIndent();

    m_outputStream << LBR_CHAR << '/' << ' ' << it->second << ' ' << RBR_CHAR;

    // Ściągamy blok ze stosu.
    m_blockStack.pop();

    updateColumns();
}

void ConfigWriter::closeUnclosedBlocks()
{
    while(!m_blockStack.empty())
        closeBlock();

    updateColumns();
}

void ConfigWriter::pushLineComment(std::string comment)
{
    if(!m_enableComments)
        return;

    int align = 1;

    if(m_alignmentSupport)
        align = m_commentAlignment - (m_columns % m_commentAlignment);

    // Usuń ewentualne entery
    string::iterator it = remove(comment.begin(), comment.end(), '\n');
    comment.resize(it - comment.begin());

    m_outputStream << setfill(' ') << setw(align) << ""
                   << COMMENT_CHAR << ' ' << comment;

    updateColumns();
}

void ConfigWriter::setLineCommentAlignment(unsigned int adjSize) throw()
{
    m_commentAlignment = adjSize;
}

void ConfigWriter::pushMultilineComment(std::string comment, int maxWidth)
{
    if(!m_enableComments)
        return;

    pushEmptyLine();

    istringstream istr(comment);

    if(m_alignmentSupport)
    {
        while(!istr.eof())
        {
            string line;
            getline(istr, line);

            istringstream lineistr(line);

            string tmp;

            m_outputStream << COMMENT_CHAR << ' ';
            updateColumns();

            while(!lineistr.eof())
            {
                lineistr >> tmp;
                if(m_columns + static_cast<int>(tmp.size()) > maxWidth)
                {
                    pushEmptyLine();
                    m_outputStream << COMMENT_CHAR << ' ';
                    updateColumns();
                }
                m_outputStream << tmp << ' ';
                updateColumns();
            }

            pushEmptyLine();
        }
    }
    else
    {
        while(!istr.eof())
        {
            string line;
            getline(istr, line);
            pushEmptyLine();
            m_outputStream << COMMENT_CHAR << ' ' << line;
        }
    }
}

//
// Klasa wyjątków
//

conf_writer_error::conf_writer_error(const std::string& __arg)
    : logic_error(string("Error occured when writing configuration. " + __arg))
{
}

conf_writer_error conf_writer_error::badBlock(const std::string& block)
{
    string msg;
    ostringstream stream(msg);

    stream  << "Unrecognized block '" << block << "'. ";

    return conf_writer_error(stream.str());
}

}
}
